package com.beiding.fastmock.fram;

import com.alibaba.fastjson.JSONObject;
import com.beiding.dbrequest.client.Client;
import com.beiding.dbrequest.common.Dispatcher;
import com.beiding.dbrequest.promise.Promise;
import com.beiding.dbrequest.server.Server;
import com.beiding.fastmock.exception.WrongTypeException;
import com.beiding.fastmock.fram.call.Data;
import com.beiding.fastmock.fram.common.Requester;
import com.beiding.fastmock.plugin.PluginController;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.script.Bindings;
import javax.script.SimpleBindings;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 配置器
 *
 * @author 丁常磊
 * @date 2021/7/14 10:48
 */

@Slf4j
@Component
public class Configurator {

    /**
     * 插件连接端口
     */
    @Value("${fastmock.plugin.port}")
    private Integer pluginPort;

    /**
     * 指令启动命令脚本
     * 与运行平台有关
     */
    @Value("${fastmock.plugin.run-cmd}")
    private String pluginRunCmd;

    /**
     * 插件连接主机
     */
    @Value("${fastmock.plugin.host:}")
    private String pluginHost;

    /**
     * 建立通道数量 默认为三个
     */
    @Value("${fastmock.plugin.channel-number:3}")
    private Integer channelNumber;


    /**
     * 启动超时时间
     */
    @Value("${plugin.run.timeout.seconds:10}")
    private Integer pluginRunTimeoutSeconds;

    @Autowired
    private PluginController pluginController;

    @Autowired
    private Server server;

    @Autowired
    private Dispatcher dispatcher;

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    private Map<String, Object> global = new HashMap<>();

    Bindings bindings = new SimpleBindings(global);

    @Autowired
    private ScriptFileHolder scriptFileHolder;

    @Autowired
    private FramFilter framFilter;

    /**
     * 记录所有的控制器
     */
    private Set<RequestMappingInfo> allController = new HashSet<>();

    private Set<HandlerInterceptor> allInterceptor = new HashSet<>();


    public void defineFilter(ScriptObjectMirror... objs) {

        for (ScriptObjectMirror obj : objs) {
            framFilter.getFilters().add(new ScriptFilter(obj));
        }
    }

    public void defineInterceptor(ScriptObjectMirror... objs) {

        for (ScriptObjectMirror obj : objs) {
            //将当前所在文件路径赋给该控制器对象

            //添加拦截器
            MyInterceptorRegistration interceptorRegistration = new MyInterceptorRegistration(new ScriptInterceptor(obj));

            ScriptObjectMirror pathPatterns = (ScriptObjectMirror) obj.get("patterns");
            if (pathPatterns != null) {
                List<String> ps = new ArrayList<String>((Collection) pathPatterns.values());
                interceptorRegistration.addPathPatterns(ps);
            }
            pathPatterns = (ScriptObjectMirror) obj.get("excludePatterns");
            if (pathPatterns != null) {
                List<String> ps = new ArrayList<String>((Collection) pathPatterns.values());
                interceptorRegistration.excludePathPatterns(ps);
            }

            Integer order = (Integer) obj.get("order");
            if (order != null) {
                interceptorRegistration.order(order);
            }

            HandlerInterceptor handlerInterceptor = (HandlerInterceptor) interceptorRegistration.getInterceptor();
            allInterceptor.add(handlerInterceptor);
            getInterceptors().add(handlerInterceptor);
        }

    }

    private List<HandlerInterceptor> getInterceptors() {
        try {
            Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            return (List<HandlerInterceptor>) field.get(handlerMapping);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static class MyInterceptorRegistration extends InterceptorRegistration {
        public MyInterceptorRegistration(HandlerInterceptor interceptor) {
            super(interceptor);
        }

        @Override
        protected Object getInterceptor() {
            return super.getInterceptor();
        }

        @Override
        protected int getOrder() {
            return super.getOrder();
        }
    }


    /**
     * 定义一个控制器
     *
     * @param controllerObjs 控制器定义对象
     * @return 被替换出来的控制器对象
     */
    public void defineController(ScriptObjectMirror... controllerObjs) {


        for (ScriptObjectMirror controllerObj : controllerObjs) {

            //将当前所在文件路径赋给该控制器对象
            String[] path = getStringArray(controllerObj, "path");
            if (path == null) {
                throw new NullPointerException("path为必须配置");
            }
            String[] method = getStringArray(controllerObj, "method");
            if (method == null) {
                method = new String[]{"*"};
            }
            RequestMethod[] requestMethods = toRequestMethod(method);
            String[] params = getStringArray(controllerObj, "params");
            String[] headers = getStringArray(controllerObj, "headers");
            String[] consumes = getStringArray(controllerObj, "consumes");
            String[] produces = getStringArray(controllerObj, "produces");


            RequestMappingInfo.Builder builder = RequestMappingInfo.paths(path);
            if (requestMethods != null) {
                builder = builder.methods(requestMethods);
            }
            if (params != null) {
                builder = builder.params(params);
            }
            if (consumes != null) {
                builder = builder.consumes(consumes);
            }
            if (headers != null) {
                builder = builder.headers(headers);
            }
            if (produces != null) {
                builder = builder.produces(produces);
            }
            RequestMappingInfo requestMappingInfo = builder.build();
            handlerMapping.unregisterMapping(requestMappingInfo);

            try {
                Method handle = ScriptController.class.getMethod("handle", Map.class, MultiValueMap.class, Map.class, byte[].class, HttpServletRequest.class, HttpServletResponse.class, HttpSession.class);
                handlerMapping.registerMapping(requestMappingInfo, new ScriptController(controllerObj), handle);
                allController.add(requestMappingInfo);
            } catch (NoSuchMethodException ignore) {

            }
        }

    }

    /**
     * 清除所有控制器,重新定义
     */
    public void clearController() {
        for (RequestMappingInfo requestMappingInfo : this.allController) {
            handlerMapping.unregisterMapping(requestMappingInfo);
        }
        allController.clear();
    }

    public void clearInterceptor() {
        getInterceptors().removeAll(allInterceptor);
        allInterceptor.clear();
    }

    public void clearFilter() {
        framFilter.getFilters().clear();
    }

    private RequestMethod[] toRequestMethod(String[] method) {
        if (method == null) {
            return null;
        }
        List<String> list = Arrays.asList(method);
        if (list.contains("*")) {
            return new RequestMethod[]{
                    RequestMethod.GET,
                    RequestMethod.HEAD,
                    RequestMethod.POST,
                    RequestMethod.PUT,
                    RequestMethod.PATCH,
                    RequestMethod.DELETE,
                    RequestMethod.OPTIONS,
                    RequestMethod.TRACE
            };
        }

        RequestMethod[] arr = new RequestMethod[method.length];
        for (int i = 0; i < method.length; i++) {
            arr[i] = RequestMethod.valueOf(method[i].toUpperCase());
        }
        return arr;
    }

    private String[] getStringArray(ScriptObjectMirror controllerObj, String key) {
        Object v = controllerObj.get(key);
        if (v == null) {
            return null;
        }
        if (v instanceof String) {
            return new String[]{(String) v};
        }
        if (v instanceof ScriptObjectMirror && ((ScriptObjectMirror) v).isArray()) {
            ScriptObjectMirror mirror = (ScriptObjectMirror) v;
            String[] r = new String[mirror.size()];
            mirror.forEach((k, z) -> {
                if (!(z instanceof String)) {
                    throw new WrongTypeException("类型错误");
                }
                r[Integer.valueOf(k)] = (String) z;
            });
            return r;
        }
        throw new WrongTypeException("类型错误");
    }

    public Object set(String key, Object value) {
        return this.global.put(key, value);
    }

    public Object get(String key) {
        return this.global.get(key);
    }

    public Object remove(String key) {
        return this.global.remove(key);
    }

    /**
     * 重置
     */
    public void reset() throws Exception{
        clearController();
        clearInterceptor();
        clearFilter();
        Object o = global.get("nashorn.global");
        global.clear();
        global.put("nashorn.global", o);
        loadLang();
        loadClass("Promise", Promise.class);
    }

    /**
     * 加载一个文件
     *
     * @param path
     */
    public Object loadJs(String path) {
        return scriptFileHolder.loadFile(path);
    }


    public StaticClass loadClass(String name, String clazz) throws ClassNotFoundException {
        StaticClass staticClass = StaticClass.forClass(Class.forName(clazz));
        global.put(name, staticClass);
        return staticClass;
    }


    public StaticClass loadClass(String name, Class clazz) {
        StaticClass staticClass = StaticClass.forClass(clazz);
        global.put(name, staticClass);
        return staticClass;
    }

    /**
     * 导入基础包
     */
    private void loadLang() {
        Lang.load(this.global);
    }

    public Map<String, Object> closePlugin(String name) throws Throwable {
        Promise request = server.request(name, "/plugin-client-base/close", null);
        return (Map<String, Object>) request.sync();
    }

    public Map<String, Object> pingPlugin(String name) throws Throwable {
        Promise request = server.request(name, "/plugin-client-base/ping", null);
        return (Map<String, Object>) request.sync();
    }

    public Map<String, Object> connectPlugin(String name, String host, Integer port, Integer channelNumber) throws Throwable {
        if (channelNumber == null) {
            channelNumber = this.channelNumber;
        }
        JSONObject data = new JSONObject();
        data.put("name", name);
        data.put("host", pluginHost);
        data.put("port", pluginPort);
        data.put("channelNumber", channelNumber);
        Promise promise = Client.request(host, port, "/plugin-client/connect",
                data
        );
        return (Map<String, Object>) promise.sync();
    }

    //todo 支持jar包插件,每个jar包拥有独立的运行环境(类加载器). jar包插件只能暴露一个对象

    /**
     * 启动一个插件
     *
     * @param name           插件名称
     * @param pluginFile     插件jar文件
     * @param param          启动参数
     * @param timeoutSeconds 超时等待秒数
     * @return
     */
    public Promise<Data> runPlugin(String name, File dir, String pluginFile, Map<String, Object> param, Integer timeoutSeconds) {

        if (timeoutSeconds == null) {
            timeoutSeconds = pluginRunTimeoutSeconds;
        }

        String id = UUID.randomUUID().toString();

        //拼接启动参数
        Map<String, Object> all = new HashMap<>();
        all.put("plugin.run.callback.enable", true);
        all.put("plugin.run.callback.fastmock.port", pluginPort);
        all.put("plugin.run.callback.name", name);
        all.put("plugin.run.callback.channel-number", channelNumber);
        all.put("plugin.run.callback.run-id", id);

        if (StringUtils.hasText(pluginHost)) {
            all.put("plugin.run.callback.fastmock.host", pluginHost);
        }

        if (param != null) {
            all.putAll(param);
        }

        StringBuilder pb = new StringBuilder();

        all.forEach((k, v) -> {
            pb.append("  --").append(k).append(v);
        });

        Promise<Data> promise = new Promise<>();

        //注册监听
        pluginController.registerRunCallBack(id, name, promise, timeoutSeconds);

        //交由脚本文件执行
        String cmd = pluginRunCmd.replace("@{pluginFile}", pluginFile).replace("@{parameters}", pb);

        log.info("执行命令启动插件: {}", cmd);
        scriptFileHolder.runCmd(cmd, dir);

        return promise;
    }

    public File getCurrentDir() {
        return scriptFileHolder.getCurrentDir();
    }

    public PluginClient getPlugin(String name) {
        return new PluginClient(server, name, "");
    }

}
